﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SearchEngine.DAO
{
    public class SearchOperations
    {
        private static ProbaTravelDataContext cont;
        private List<ComplexDestination> searchedList;
        private List<TravelVariant> variantList;

        static SearchOperations()
        {
            cont = new ProbaTravelDataContext();
        }

        public SearchOperations(long startID, long endID)
        {
            searchedList = GetFullList(startID, endID);
            variantList = GetVariants(searchedList);
        }


        private static List<TravelVariant> GetVariants(List<ComplexDestination> searchedList)
        {
            List<TravelVariant> result = new List<TravelVariant>();

            foreach (ComplexDestination compl in searchedList)
            {
                List<long> dest = GetAllDestination(compl.settlementID.First());
                List<long> nextDest = GetAllDestination(compl.settlementID.ElementAt(1));
                List<long> intersectLast = dest.Intersect(nextDest).ToList();
                List<long> intersectNew = Clone(intersectLast);

                int counterLast = 1;
                TravelVariant travel = new TravelVariant();
                travel.SettlementID.Add(compl.settlementID.First());

                while (counterLast < compl.settlementID.Count)
                {
                    while (intersectNew.Count > 0)
                    {
                        counterLast++;
                        intersectLast = Clone(intersectNew);
                        if (counterLast == compl.settlementID.Count) { break; }
                        nextDest = GetAllDestination(compl.settlementID.ElementAt(counterLast));
                        intersectNew = intersectLast.Intersect(nextDest).ToList();
                    }
                    counterLast--;
                    if (counterLast == compl.settlementID.Count - 1)
                    {
                        travel.SettlementID.Add(compl.settlementID.ElementAt(counterLast));
                        break;
                    }
                    travel.SettlementID.Add(compl.settlementID.ElementAt(counterLast));
                    dest = GetAllDestination(compl.settlementID.ElementAt(counterLast));
                    nextDest = GetAllDestination(compl.settlementID.ElementAt(counterLast + 1));
                    intersectLast = dest.Intersect(nextDest).ToList();
                    intersectNew = Clone(intersectLast);
                    counterLast++;
                }
                result.Add(travel);
            }

            return result;
        }

        private static List<ComplexDestination> GetFullList(long startPoint, long endPoint)
        {
            //startPointID = startPoint;
            //endPointID = endPoint;
            List<ComplexDestination> complexDestList = new List<ComplexDestination>();
            List<ComplexDestination> searchedList = new List<ComplexDestination>();
            List<ComplexDestination> listToRemove = new List<ComplexDestination>();
            List<ComplexDestination> listToAdd = new List<ComplexDestination>();
            ComplexDestination compl = new ComplexDestination();
            compl.settlementID.Add(startPoint);
            complexDestList.Add(compl);

            while (complexDestList.Count > 0)
            {
                foreach (ComplexDestination complexDestination in complexDestList)
                {
                    if (complexDestination.settlementID.Last() == endPoint)
                    {
                        searchedList.Add(complexDestination);
                        listToRemove.Add(complexDestination);
                    }
                    else
                    {
                        List<long> allNextSettlementsID =
                            GetAllNextSettlementsID(complexDestination.settlementID.Last());

                        foreach (long settlementID in allNextSettlementsID)
                        {
                            if (!complexDestination.settlementID.Contains(settlementID))
                            {
                                ComplexDestination complex = new ComplexDestination();
                                complex.settlementID = Clone(complexDestination.settlementID);
                                complex.settlementID.Add(settlementID);
                                listToAdd.Add(complex);
                            }
                        }

                        listToRemove.Add(complexDestination);
                    }
                }

                foreach (ComplexDestination complAdd in listToAdd)
                {
                    complexDestList.Add(complAdd);
                }

                foreach (ComplexDestination complRem in listToRemove)
                {
                    complexDestList.Remove(complRem);
                }

                listToRemove.Clear();
                listToAdd.Clear();
            }

            return searchedList;
        }

        private static List<long> Clone(List<long> list)
        {
            List<long> newList = new List<long>();

            foreach (long l in list)
            {
                newList.Add(l);
            }

            return newList;
        }

        private static List<long> GetAllNextSettlementsID(long settlementID)
        {
            List<long> destinationListID = GetAllDestination(settlementID);
            List<long> allNextSettlementsID = new List<long>();

            foreach (long destinationID in destinationListID)
            {
                long nextSettlementID = GetNextSettlementID(destinationID, settlementID);

                if (nextSettlementID != -1)
                {
                    allNextSettlementsID.Add(nextSettlementID);
                }
            }

            return allNextSettlementsID;
        }

        private static long GetNextSettlementID(long destinstionID, long settlementID)
        {
            int currentOrderNumber = GetCurrentOrderNumber(destinstionID, settlementID);

            var orderNumberList = from d in cont.Destinations
                                  join w in cont.WayStations
                                  on d.DestinationID equals w.DestinationID
                                  where d.DestinationID == destinstionID & w.WayStationOrderNumber > currentOrderNumber
                                  orderby w.WayStationOrderNumber
                                  select w.SettlementID;

            if (orderNumberList.ToList().Count > 0)
            {
                return orderNumberList.ToList().First();
            }
            else
            {
                return -1;
            }
        }

        private static int GetCurrentOrderNumber(long destinstionID, long settlementID)
        {
            var currentNumber = (from d in cont.Destinations
                                 join w in cont.WayStations
                                 on d.DestinationID equals w.DestinationID
                                 where d.DestinationID == destinstionID & w.SettlementID == settlementID
                                 select w.WayStationOrderNumber).First();

            return currentNumber;
        }

        private static List<long> GetAllDestination(long settlementID)
        {
            var destinationListID = from d in cont.Destinations
                                    join w in cont.WayStations
                                    on d.DestinationID equals w.DestinationID
                                    where w.SettlementID == settlementID
                                    select d.DestinationID;

            return destinationListID.ToList();
        }

        private static List<WayStation> GetAllWayStationForSettlement(long settlementID)
        {
            var allWay = from w in cont.WayStations
                         where w.SettlementID == settlementID
                         select w;

            return allWay.ToList();
        }

        private static long GetWayStationID(long destinationID, long settlementID)
        {
            var wayStationID = (from d in cont.Destinations
                                join w in cont.WayStations
                                on d.DestinationID equals w.DestinationID
                                where d.DestinationID == destinationID & w.SettlementID == settlementID
                                select w.WayStationID).First();

            return wayStationID;
        }

        private static List<RoadSegment> GetRoadSegments(long settlementStartID, long settlementEndID)
        {
            List<RoadSegment> roadSegmentList = new List<RoadSegment>();

            var destStart = from d in cont.Destinations
                            join w in cont.WayStations
                            on d.DestinationID equals w.DestinationID
                            where w.SettlementID == settlementStartID
                            select w.DestinationID;

            var destEnd = from d in cont.Destinations
                          join w in cont.WayStations
                          on d.DestinationID equals w.DestinationID
                          where w.SettlementID == settlementEndID
                          select w.DestinationID;

            foreach (long l in destStart.ToList().Intersect(destEnd).ToList())
            {
                RoadSegment roadSegment = new RoadSegment();
                roadSegment.StartWayStationID = GetWayStationID(l, settlementStartID);
                roadSegment.EndWayStationID = GetWayStationID(l, settlementEndID);
                roadSegmentList.Add(roadSegment);
            }
            return roadSegmentList;
        }

        public List<Route> GetAllRoutes(DateTime date)
        {
            List<Route> routeList = new List<Route>();

            foreach (TravelVariant tVariant in variantList)
            {
                List<RoadSegment> rsList = GetRoadSegments(tVariant.SettlementID.ElementAt(0),
                    tVariant.SettlementID.ElementAt(1));

                foreach (RoadSegment rSegment in rsList)
                {
                    List<DateTime> startHours = GetStartHours(rSegment, date);

                    foreach (DateTime startHour in startHours)
                    {
                        TimedRoadSegment trSegment = new TimedRoadSegment(rSegment, startHour);
                        Route route = new Route();
                        route.trsList.Add(trSegment);

                        for (int i = 1; i < tVariant.SettlementID.Count - 1; i++)
                        {
                            route.trsList.Add(GetNextTimedRoadSegment(trSegment, tVariant.SettlementID.ElementAt(i),
                                tVariant.SettlementID.ElementAt(i + 1)));
                        }

                        routeList.Add(route);
                    }
                }
            }
            return routeList;
        }

        private TimedRoadSegment GetNextTimedRoadSegment(TimedRoadSegment trSegment, long startSettlementID,
            long endSettlementID)
        {
            List<RoadSegment> rsList = GetRoadSegments(startSettlementID, endSettlementID);
            List<DateTime> startHours = new List<DateTime>();
            DateTime temp;
            DateTime min;
            RoadSegment searchedSegment = new RoadSegment();

            int timeForRoadSegment = GetTimeForRoadSegment(trSegment.RoadSegment.StartWayStationID,
                trSegment.RoadSegment.EndWayStationID, false);

            DateTime arriveHour = trSegment.StartHour.AddMinutes(timeForRoadSegment);

            temp = arriveHour;
            min = arriveHour.AddDays(8);

            foreach (RoadSegment rs in rsList)
            {
                //here!!
                    while (startHours.Count == 0)
                    {
                        foreach (DateTime d in GetStartHours(rs, temp))
                        {
                            if (d > arriveHour)
                            {
                                startHours.Add(d);
                                break;
                            }
                        }
                        temp=temp.AddDays(1);
                    }

                foreach (DateTime d in startHours)
                {
                    if (d < min)
                    {
                        min = d;
                        searchedSegment = rs;
                    }
                }
                startHours.Clear();
                temp = arriveHour;
            }

            return new TimedRoadSegment(searchedSegment, min);
        }

        private static long GetDestinationIDForWayStation(long wayStationID)
        {
            var destinationID = (from d in cont.Destinations
                                 join w in cont.WayStations
                                 on d.DestinationID equals w.DestinationID
                                 where w.WayStationID == wayStationID
                                 select d.DestinationID).First();

            return destinationID;
        }

        private static List<DateTime> GetStartHours(RoadSegment rSegment, DateTime date)
        {
            List<DateTime> result = new List<DateTime>();
            long currentDestinationID = GetDestinationIDForWayStation(rSegment.StartWayStationID);

            var firstWayStationID = (from w in cont.WayStations
                                     where w.DestinationID == currentDestinationID
                                     & w.WayStationOrderNumber == 1
                                     select w.WayStationID).First();

            //Time from the first WayStation to the begining of the current segment
            int time = GetTimeForRoadSegment(firstWayStationID, rSegment.StartWayStationID, true);

            int dayOfWeekIDPrevious = (int)date.AddDays(-1).DayOfWeek;

            var startHoursPrevious = from d in cont.Destinations
                                     join s in cont.StartHours
                                     on d.DestinationID equals s.DestinationID
                                     where s.SheduleTypeID == dayOfWeekIDPrevious & s.StartHourValue >= (1440 - time)
                                     & d.DestinationID == currentDestinationID
                                     orderby s.StartHourValue
                                     select s.StartHourValue;

            foreach (int i in startHoursPrevious)
            {
                //DateTime d = new DateTime(date.Year, date.Month, date.Day + 1, (i + time - 1440) / 60,
                //    (i + time - 1440) % 60, 0);
                DateTime d = new DateTime(date.Year, date.Month, date.Day, 0, 0, 0).AddMinutes(i + time);
                result.Add(d);
            }

            int dayOfWeekIDPresent = (int)date.DayOfWeek;

            var startHoursPresent = from d in cont.Destinations
                                    join s in cont.StartHours
                                    on d.DestinationID equals s.DestinationID
                                    where s.SheduleTypeID == dayOfWeekIDPresent & s.StartHourValue <= (1440 - time)
                                    & d.DestinationID == currentDestinationID
                                    orderby s.StartHourValue
                                    select s.StartHourValue;

            foreach (int i in startHoursPresent)
            {
                //DateTime d = new DateTime(date.Year, date.Month, date.Day + 1, (i + time) / 60,
                //    (i + time) % 60, 0);
                DateTime d = new DateTime(date.Year, date.Month, date.Day, 0, 0, 0).AddMinutes(i + time);
                result.Add(d);
            }

            return result;
        }

        private static int GetTimeForRoadSegment(long startWayStationID,
            long endWayStationID, bool lastStayInclude)
        {
            int result;
            if (startWayStationID == endWayStationID)
            {
                result = 0;
            }
            else
            {
                long destinationID = (from w in cont.WayStations
                                      where w.WayStationID == startWayStationID
                                      select w.DestinationID).First();

                int startOrderNumber = (from w in cont.WayStations
                                        where w.WayStationID == startWayStationID
                                        select w.WayStationOrderNumber).First();

                int endOrderNumber = (from w in cont.WayStations
                                      where w.WayStationID == endWayStationID
                                      select w.WayStationOrderNumber).First();

                var timeTravelList = from w in cont.WayStations
                                     where w.DestinationID == destinationID & w.WayStationOrderNumber > startOrderNumber
                                     & w.WayStationOrderNumber <= endOrderNumber
                                     select w.WayStationTimeTo;

                int timeTravel;

                //if (timeTravelList.ToList().Count == 0)
                //{
                //    timeTravel = 0;
                //}
                //else
                //{

                    timeTravel = (from w in cont.WayStations
                                  where w.DestinationID == destinationID & w.WayStationOrderNumber > startOrderNumber
                                  & w.WayStationOrderNumber <= endOrderNumber
                                  select w.WayStationTimeTo).Sum();
                //}

                int timeStay;

                if (lastStayInclude)
                {
                    var timeStayList = from w in cont.WayStations
                                       where w.DestinationID == destinationID & w.WayStationOrderNumber > startOrderNumber
                                       & w.WayStationOrderNumber <= endOrderNumber
                                       select w.WayStationStay;

                    //if (timeStayList.ToList().Count == 0)
                    //{
                    //    timeStay = 0;
                    //}
                    //else
                    //{
                        timeStay = (from w in cont.WayStations
                                    where w.DestinationID == destinationID & w.WayStationOrderNumber > startOrderNumber
                                    & w.WayStationOrderNumber <= endOrderNumber
                                    select w.WayStationStay).Sum();
                    //}
                }
                else
                {
                    var timeStayList = from w in cont.WayStations
                                       where w.DestinationID == destinationID & w.WayStationOrderNumber > startOrderNumber
                                       & w.WayStationOrderNumber < endOrderNumber
                                       select w.WayStationStay;

                    if (timeStayList.ToList().Count == 0)
                    {
                        timeStay = 0;
                    }
                    else
                    {
                        timeStay = (from w in cont.WayStations
                                    where w.DestinationID == destinationID & w.WayStationOrderNumber > startOrderNumber
                                    & w.WayStationOrderNumber < endOrderNumber
                                    select w.WayStationStay).Sum();
                    }
                }
                result = timeStay + timeTravel;
            }

            return result;
        }
    }
}
